#pragma once

#include "zen_vap_helper.h"
#include "zen_matrix.h"
#include "zen_app.h"
#include "zen_vap_object.h"
#include "zen_vap_texture.h"
#include "zen_vap_touch.h"
#include <vector>
#include <mutex>

/*!
@header zen_vap_node.h
@abstract basic class node and the part-class.
@author mehertj
@version v1.0
*/
namespace Zen { namespace Vap {
	class Node;

	class NodeDelegate {
	public:
		virtual ~NodeDelegate() = default;

		virtual void load() = 0;
		virtual void unload() = 0;
		virtual void ready() = 0;
		virtual void draw() = 0;

//		virtual Node * touchDown(AppTouch const & touch) = 0;
//		virtual Node * touchMove(AppTouch const & touch) = 0;
//		virtual Node * touchUp(AppTouch const & touch) = 0;
//		virtual Node * touchCancel(AppTouch const & touch) = 0;
	};

	/**
	 @ref zen_vap_action.h
	 */
	class Action;

	class Node : public Object, public NodeDelegate
	{
	public:
		Node(std::string const & name = "Node");

		virtual ~Node();

//		virtual Node * touchDown(AppTouch const & touch) override; // { return nullptr; }
//		virtual Node * touchMove(AppTouch const & touch) override { return this; }
//		virtual Node * touchUp(AppTouch const & touch) override { return nullptr; }
//		virtual Node * touchCancel(AppTouch const & touch) override { return nullptr; }

	public:
		virtual void load() override {}
		
		virtual void unload() override {}

		virtual void ready() override {}

		virtual void draw() override {}

//		void drawNodes();
	public:
		/**
		 if not visible, the draw will not be called.
		 */
		void setVisible(bool);

		auto isVisible() const { return m_is_visible; }


	public: // greyfied
		void setGreyfied(bool greyfy) { m_greyfied = greyfy; }

		bool isGreyfied() { return m_greyfied; }

		void setGreyFactor(Vec3 const & factor) { m_grey_factor	 = factor; }

		Vec3 getGreyFactor() { return m_grey_factor; }

	public: // as a upper

		void addNode(std::shared_ptr<Node> node);

		void removeAllNodes();

		void validateSubNodesOrder();

	public: // as a child
		void addTo(std::shared_ptr<Node> parent);
		/**
		 @function remove
		 remove this node from tree.
		 - if the node has already add to other upper <Node>,  dont <delete> it anymore,
		 you can remove() this node, or remove the upper node, it will be deleted automatically after current Update frame.
		 - or please <delete> if <new>ed.
		 */
		void remove();

		/**
		 @function setOrder
		 - drawing order, just compare with brother-nodes.
		 - less first draw. so larger shows in front.
		 */
		void setOrder(int index);

		auto getOrder() const { return m_order; }

		std::shared_ptr<Node> getParent() { return m_parent.lock(); }
		
	public: // action.
		void runAction(std::shared_ptr<Action> action);

		void stopAllActions();

	protected:

		std::weak_ptr<Node> m_parent;
		int m_order = 0;
		bool m_is_visible = true;

		bool m_greyfied = false;
		Vec3 m_grey_factor = { 0.21f, 0.71f, 0.08f };
	protected:
		
		std::vector<std::shared_ptr<Node> > m_nodes;
		bool m_sub_nodes_dirty = false;
	};

	/// @class Node2D : Node
	class Node2D : public Node
	{
	public:
		Node2D(std::string const & name = "Node2D");
	public:
		void setBlend(eBlend blend) { m_blend = blend; }

		auto getBlend() { return m_blend; }

		void setAlpha(float alpha) { m_alpha = alpha; }

		auto getAlpha() { return m_alpha; }
	protected:

		eBlend m_blend = eBlend::Normal;
		float m_alpha = 1.0f;
	};

	class Node3D : public Node
	{
	public:
		Node3D(std::string const & name = "Node3D");

		void setCullFace(eFace face) { m_cull_face = face; }

		auto getCullFace() { return m_cull_face; }

		void setDepthEnabled(bool e) { m_is_depth_enabled = e; }

		auto isDepthEnabled() { return m_is_depth_enabled; }

		void setDepthWritable(bool w) { m_is_depth_writable = w;}

		auto isDepthWritable() { return m_is_depth_writable; }

	protected:
		eFace m_cull_face = eFace::Back;
		bool m_is_depth_enabled = true;
		bool m_is_depth_writable = true;
	};

	/// node part: transforming attribute
	class PartOfMatrix {
	public:
		PartOfMatrix() = default;

		virtual ~PartOfMatrix() = default;

		Mat4 & getMatrix() { this->updateMatrix(); return m_final_matrix; }

		auto getMatrixVersion() { return m_mat_version; }

		void setMatrix(Mat4 const & matrix, int parent_version);

		bool isMatExpiredFromParent(int parent_version);

		virtual void updateMatrix() {};
	protected:
		Mat4 m_final_matrix;
		int m_mat_version = 0;
		int m_parent_mat_version = 0;
	};

	class PartOfMatrix2D : public PartOfMatrix {
	public:
		PartOfMatrix2D(bool is_anchor_valid = true);

		void updateMatrix() override;
	public:
		void setPosition(Vec2 pos);
		void setX(float x);
		void setY(float y);

		void setScale  (Vec2 scale);
		void setScaleX (float sx);
		void setScaleY (float sy);

		void setSkew  (Vec2 skew);
		void setSkewX (float kx);
		void setSkewY (float ky);

		void setRotation(float radians);

		void setAnchor(Vec2 anchor);
		void setAnchorX(float ax);
		void setAnchorY(float ay);

		auto const & getPosition() { return m_pos; }
		auto const & getX() { return m_pos.x; }
		auto const & getY() { return m_pos.y; }
		auto const & getScale() { return m_scale; }
		auto const & getScaleX() { return m_scale.x; }
		auto const & getScaleY() { return m_scale.y; }
		auto const & getSkew() { return m_skew; }
		auto const & getSkewX() { return m_skew.x; }
		auto const & getSkewY() { return m_skew.y; }
		auto const & getRotation() { return m_rotation; }
		auto const & getAnchor() { return m_anchor; }
		auto const & getAnchorX() { return m_anchor.x; }
		auto const & getAnchorY() { return m_anchor.y; }

		void setAnchorEnabled(bool enable);
		bool isAnchorEnabled();
	protected:
		Mat4 m_matrix0 = Mat4MakeIdentity();
		Vec2 m_pos = { 0, 0 };
		Vec2 m_scale = { 1, 1 };
		Vec2 m_skew = { 1, 1 };
		Vec2 m_scale2 = { 1, 1 };
		Vec2 m_anchor = { 0, 0 };
		float m_rotation = 0;
		bool m_is_matrix_dirty = true;
		bool m_is_anchor_valid = true;

	protected:
		void setScale2_(Vec2);
		Vec2 const & getScale2_() { return m_scale2; }
	};

	class PartOfMatrix3D : public PartOfMatrix {
	public:
		PartOfMatrix3D();
		void updateMatrix() override;

	public:
		void setPosition(cVec3 & pos);
		void setScale  (cVec3 & scale);
		void setRotationAngle(float rad);
		void setAnchor(cVec3 & anchor);
		void setSize(cVec3 & size);
		void setRotationAxis(cVec3 &);

		Vec3 const & getPosition() { return m_view_pos; }
		Vec3 const & getScale() { return m_view_scale; }
		Vec3 const & getAnchor() { return m_view_anchor; }
		Vec3 const & getSize() { return m_view_size; }
		Vec3 const & getRotationAxis() { return m_view_rotation_axis; }
		auto getRotationAngle() { return m_view_rotation_angle; }

#define GET_SET(Name, name) \
float get##Name##X() { return m_view_##name.x; } \
float get##Name##Y() { return m_view_##name.y; } \
float get##Name##Z() { return m_view_##name.z; } \
void set##Name##X(float v) { m_view_##name.x = v; m_is_matrix_dirty = true; } \
void set##Name##Y(float v) { m_view_##name.y = v; m_is_matrix_dirty = true; } \
void set##Name##Z(float v) { m_view_##name.z = v; m_is_matrix_dirty = true; } \

		GET_SET(Position, pos)
		GET_SET(Scale, scale)
		GET_SET(Anchor, anchor)
		GET_SET(Size, size)
		GET_SET(RotationAxis, rotation_axis)

#undef GET_SET
	protected:
		Mat4 m_matrix0 = Mat4MakeIdentity();
		Vec3 m_view_pos = { 0, 0, 0 };
		Vec3 m_view_scale = { 1, 1, 1 };
		Vec3 m_view_rotation_axis = { 0, 0, 1 };
		Vec3 m_view_size = { 1, 1, 1 };
		Vec3 m_view_anchor = { 0, 0 };
		float m_view_rotation_angle = 0;
		bool m_is_matrix_dirty = true;
	};

	/// node attribute: color
	/// that multiply to final pixel.
	class PartOfColor
	{
	public:
		PartOfColor() = default;

		virtual ~PartOfColor() = default;

		void setColor(Color4f const & color);

		Color4f const & getColor() { return m_color; }

	protected:
		Color4f m_color = Color4f(eColor::White);
		bool m_is_color_dirty = true;
	};

	/// flip the texture.
	class PartOfFlip
	{
	public:
		PartOfFlip() = default;

		virtual ~PartOfFlip() = default;

		void setFlipX(bool x);

		void setFlipY(bool y);

		bool isFlipX();

		bool isFlipY();

	protected:
		bool m_is_flip_x = false;
		bool m_is_flip_y = false;
		bool m_is_flip_dirty = true;
	};

	/// texture
	class PartOfTexture
	{
	public:

		void setTexture(SharedTexture texture);

		auto getTexture() { return m_texture; }

		void setTextureRect(Zen::Rect4f rect);

		auto getTextureRect() { return m_texture_rect; }

		void setTextureScale(Vec2 scale);

		auto getTextureScale() { return m_texture_scale; }

	protected:
		virtual bool updateTexture_();
		
		SharedTexture m_texture;
		Zen::Rect4f m_texture_rect = { 0, 0, 1, 1 };
		Size2 m_texture_size = { 0, 0 };
		Vec2 m_texture_scale = { 1, 1 };
		/// m_texture or m_texture_rect change.
		bool m_is_texture_dirty = true;

	};
	
	class DrawStack
	{
	public:
		static DrawStack * Get();

		void reset();

		auto getTopMat4() { return m_mats.back().mat; }

		auto getTopMat4Version() { return m_mats.back().version; }

		auto getTopAlpha() { return m_alphas.back(); }

		auto getTopBlend() { return m_blends.back(); }

	protected:
		DrawStack();

		virtual ~DrawStack();

		void pushMatrix(Mat4 const * mat, int version);

		void popMatrix();

		void pushAlpha(float alpha);

		void popAlpha();

		void pushBlend(eBlend);

		void popBlend();

	protected:
		struct MatrixInfo {
			Mat4 const * mat;
			int version;
		};
		std::vector<MatrixInfo> m_mats;
		std::vector<float> m_alphas;
		std::vector<eBlend> m_blends;

	public:

		struct GuardMatrix
		{
			GuardMatrix(Mat4 const * mat, int version)
			{
				Get()->pushMatrix(mat, version);
			}
			~GuardMatrix()
			{
				Get()->popMatrix();
			}
		};

		struct GuardAlpha
		{
			GuardAlpha(float alpha)
			{
				Get()->pushAlpha(alpha);
			}
			~GuardAlpha()
			{
				Get()->popAlpha();
			}
		};

		struct GuardBlend
		{
			GuardBlend(eBlend blend)
			{
				Get()->pushBlend(blend);
			}
			~GuardBlend()
			{
				Get()->popBlend();
			}
		};
	};
}}
